package com.ruoyi.app.aspectj;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.app.config.AppConfig;
import com.ruoyi.app.util.ApiSecurityUtils;
import com.ruoyi.app.util.ReqMsg;
import com.ruoyi.app.util.RspMsg;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.RspEnmu;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * API安全性校验处理
 *
 * @author ruoyi
 */
@Slf4j
@Aspect
@Component
public class ApiSecurityAspect {

    private static final String TRACE_ID = "traceId";// 问题追踪ID 在日志文件中引用
    private static final String METHOD_NAME = "methodName";// 方法名

    @Autowired
    private AppConfig appConfig;

    @Autowired
    private ISysConfigService iSysConfigService;

    @Autowired
    TokenService tokenService;

    @Autowired
    RedisCache redisCache;

    // 配置切入点
    @Pointcut("@annotation(com.ruoyi.app.annotation.AppSecurity)")
    public void apiPointCut() {
    }

    @Around("apiPointCut()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest request = ServletUtils.getRequest();
        String methodName = pjp.getSignature().getName();// 方法名
        String traceId = MDC.get(TRACE_ID);
        if (null == traceId) {
            traceId = IdUtils.fastSimpleUUID();
            if (null != tokenService.getLoginUser(ServletUtils.getRequest())) {
                String userId = tokenService.getLoginUser(ServletUtils.getRequest()).getUsername();// 注意这里
                if (!StringUtils.isBlank(userId)) {
                    traceId = userId;
                }
            }

            // user_id为空 取 newFingerprint
            String newFingerprint = request.getHeader("newFingerprint");
            if (null != request.getHeader("newFingerprint")) {
                traceId = newFingerprint;
            }
        }

        MDC.put(TRACE_ID, traceId);
        MDC.put(METHOD_NAME, methodName);

        log.info("HTTP URL : " + request.getRequestURL().toString());
        log.info("HTTP METHOD : " + request.getMethod());
        log.info("CLASS_METHOD : " + pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName());

        try {
            log.info("APP Header : {}", ServletUtils.getHeaders(request));
        } catch (Exception e) {
            log.error("APP Header", e.getMessage());
        }
        ReqMsg reqMsg = null;
        try {
            String appVersion = request.getHeader("appVersion");
            log.info("APP VERSION : {}", appVersion);
            // 20220818 根据运营邮件要求不再支持 2.2.0 以下版本
            if (StringUtils.isBlank(appVersion) || Integer.valueOf(appVersion.replace(".", "")) < 220) {
                log.info("appVersion is null or version[{}] is too low and no longer supported", appVersion);
                throw new BusinessException(RspEnmu.APP_VERSION_DONOT_SUPPORT);
            }

            String fingerprint = request.getHeader("fingerprint");
            String currentVersion = iSysConfigService.selectConfigByKey("current_version");
            String affectedVersion = iSysConfigService.selectConfigByKey("affected_version");
            String versionPopupNums = iSysConfigService.selectConfigByKey("version_popup_nums");
            List<Date> list = redisCache.getCacheList("update_popup_nums:" + fingerprint);

            if (!appVersion.equals(currentVersion) && affectedVersion.contains(appVersion) && list.size() < Integer.parseInt(versionPopupNums)) {
                //app传来的版本不等于当前版本
                //并且受影响版本包含app传来的版本
                //并且该设备未达到推送上限
                JSONObject jsonObject = new JSONObject();

                List<Date> list1 = new ArrayList<>();
                list1.add(new Date());
                redisCache.setCacheList("update_popup_nums:" + fingerprint, list1);

                String versionPopupContent = iSysConfigService.selectConfigByKey("version_popup_content");
                String versionPopupType = iSysConfigService.selectConfigByKey("version_popup_type");

                jsonObject.put("versionPopupContent", versionPopupContent);
                jsonObject.put("versionPopupType", versionPopupType);
                log.info("required popup,popup data:{}", jsonObject);
                throw new BusinessException(jsonObject.toString(), RspEnmu.APP_VERSION_TO_LOW.getCode());
            }

            reqMsg = (ReqMsg) pjp.getArgs()[0];
            log.info("APP调用请求参数:" + reqMsg);
        } catch (Exception exp) {
            exp.printStackTrace();
            if (exp instanceof BusinessException) {
                BusinessException e = (BusinessException) exp;
                if (RspEnmu.APP_VERSION_DONOT_SUPPORT.getCode().equals(e.getCode()) || RspEnmu.APP_VERSION_TO_LOW.getCode().equals(e.getCode())) {
                    throw e;
                }
            }

            log.error("==接口参数格式非法==");
            log.error("异常信息：{}", exp.getMessage());
            throw new BusinessException(RspEnmu.ILLEGAL_ARGUMENT);
        }

        if (!ApiSecurityUtils.securityVerify(reqMsg, appConfig)) {
            log.warn("加密请求验签不通过");
            throw new BusinessException(RspEnmu.ILLEGAL_REQ_ERR);
        }

        //解密
        ReqMsg newReqMsg = ApiSecurityUtils.decodeReq(reqMsg, appConfig.getServicePrivatekey());
//        log.info("解密后的请求参数：{}", JSON.toJSONString(newReqMsg));
        pjp.getArgs()[0] = newReqMsg;

        Object ob = pjp.proceed();//ob为方法的返回值

        //加密
        RspMsg rsp = (RspMsg) ob;
        log.info("加密前的返回结果：{}", JSON.toJSONString(rsp));
        rsp = ApiSecurityUtils.encodeReq(reqMsg, rsp);
//        log.info("加密后的返回结果：{}", JSON.toJSONString(rsp));

        MDC.clear();
        return rsp;
    }

}